unit uMainForm;

interface

uses
  OnyxCore, Windows, Messages, SysUtils, Variants, Classes, Graphics,
  Controls, Dialogs, Forms, OnyxDialogs, OnyxStrings, OnyxGUI,
  MarbleVapors, MarbleAgents, MarbleLists, MarbleGraphics, MarbleSimits,
  MarbleMath, MarbleToys, 
  StdCtrls, Buttons, ExtCtrls, ComCtrls;

type
  TSpaceItem = class(TSimit)
    public
    constructor Go; override;
    procedure GravityPull(SpaceItem: TSpaceItem);
    procedure Collide(OtherThing: TSimit); override;
    end;

  TTwinkle = class(TAction)
    public
    class function CanGuide(Hopeful: TObject): boolean; override;
    procedure Whatever; override;
    end;

  TMainForm = class(TForm)
    SetUpPnl: TPanel;
    StatusBar: TStatusBar;
    Label7: TLabel;
    Panel1: TPanel;
    Label5: TLabel;
    ObjectMassCB: TComboBox;
    ObjectMassVariationCB: TComboBox;
    ObjectSpeedVariationCB: TComboBox;
    ObjectSpeedCB: TComboBox;
    Label6: TLabel;
    Distance: TLabel;
    ObjectDistanceCB: TComboBox;
    ObjectDistanceVariationCB: TComboBox;
    ObjectCountCB: TComboBox;
    Label1: TLabel;
    AddObjectsBtn: TBitBtn;
    DeleteObjectsBtn: TBitBtn;
    Panel2: TPanel;
    Label2: TLabel;
    EquationLbl: TLabel;
    GCB: TComboBox;
    Label3: TLabel;
    DistancePowerCB: TComboBox;
    Label4: TLabel;
    UpdatePerSecCB: TComboBox;
    Label8: TLabel;
    Simulate: TLabel;
    ApplyBtn: TBitBtn;
    SimIntervalCB: TComboBox;
    Panel4: TPanel;
    StartSimulationBtn: TBitBtn;
    StopSimulationBtn: TBitBtn;
    ResetCamaraBtn: TBitBtn;
    TrailsCheck: TCheckBox;
    FindLargestMassBtn: TBitBtn;
    NextMassBtn: TBitBtn;
    AddShipBtn: TBitBtn;
    DriftTurnCheck: TCheckBox;
    SeedEdit: TEdit;
    Label9: TLabel;
    FramesPerSecEdit: TEdit;
    Label10: TLabel;
    Timer: TTimer;
    procedure FormCreate(Sender: TObject);
    procedure AddObjects(Sender: TObject);
    procedure Orbit(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormKeyPress(Sender: TObject; var Key: Char);
    procedure UpdateSettings(Sender: TObject);
    procedure DeleteObjectsBtnClick(Sender: TObject);
    procedure ResetCamaraBtnClick(Sender: TObject);
    procedure FormResize(Sender: TObject);
    procedure StartSimulationBtnClick(Sender: TObject);
    procedure StopSimulationBtnClick(Sender: TObject);
    procedure NextMassBtnClick(Sender: TObject);
    procedure AddShipBtnClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure TimerTimer(Sender: TObject);
  private
    procedure SetValue(CB: TComboBox; var Variable: double);
    procedure DrawSpaceObjects;
    procedure UpdateSpaceObjects;
    procedure Drift(SpaceItem: TSpaceItem);
    procedure Draw(SpaceItem: TSpaceItem);
  public
    FShownOnce: boolean;
    FTwinkle: TTwinkle;
    FShip: TSpaceItem;
    FFramesPerSec: Int32;
    FObjectMass: TFloat;
    FSpeed: TFloat;
    FDistance: TFloat;
    FObjectMassV: TFloat;
    FSpeedV: TFloat;
    FDistanceV: TFloat;
    FObjectCount: longint;
    FUpdateCount: longint;
    FSpaceItems: TVaporList;
    FUpdatesPerSec: longint;
    FGravity: TVector;
    FBackgroundBitmap: TBitmap;
    FBackground: TVCLSurface;
    FForground: TVCLSurface;
    FCamara: TCamera;
    FG: TFloat;
    FDistancePower: TFloat;
    FSimInterval: TFloat;
    FOrbiting: boolean;
    FTempVec: TVector;
  end;

var
  MainForm: TMainForm;

implementation

uses Math;

{$R *.dfm}

//----------------------------------------------------------------------
procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
FOrbiting := false;
end;
//----------------------------------------------------------------------
procedure TMainForm.FormCreate(Sender: TObject);
begin
UpdateSettings(nil);
RandSeed := StrToInt(SeedEdit.Text);
FBackgroundBitmap := TBitmap.Create;
FBackgroundBitmap.Width := ClientWidth;
FBackgroundBitmap.Height := ClientHeight;
VaporInit(FSpaceItems, TVaporList.Go);
VaporInit(FGravity, TVector.Go);
VaporInit(FBackground, TVCLBitMapSurface.Create(FBackgroundBitmap, ClientWidth, ClientHeight));
VaporInit(FForground, TVCLSurface.Create(Canvas, ClientWidth, ClientHeight));
VaporInit(FCamara, TCamera.Create(FBackground, TVector.Go, TVector.Go, 1, 1));
VaporInit(FTempVec, TVector.Create);
VaporInit(FTwinkle, TTwinkle.Go);
DoubleBuffered := true;
end;
//------------------------------------------------------------------------------
procedure TMainForm.FormDestroy(Sender: TObject);
begin
FSpaceItems.FreeRef;
FGravity.FreeRef;
FBackground.FreeRef;
FForground.FreeRef;
FCamara.FreeRef;
FTempVec.FreeRef;
FTwinkle.FreeRef;
end;
//----------------------------------------------------------------------
procedure TMainForm.AddObjects(Sender: TObject);
var
  Index: longint;
  NewMass: TFloat;
  Rock: TSpaceItem;
begin
UpdateSettings(Sender);
for Index := 1 to FObjectCount do
  begin
  NewMass := RandomDivMul(FObjectMass, FObjectMassV);
  Rock := TSpaceItem.Create(
              TPolyArt.Go,
              TVector.CreatePolar(RandomDivMul(FDistance, FDistanceV), RandomAngle),
              RandomAngle,
              power(NewMass, 1/3),
              TVector.CreatePolar(RandomDivMul(FSpeed, FSpeedV), RandomAngle),
              RandomFloatRange(-0.5, 0.5),
              NewMass);
  FSpaceItems.AddItem(Rock);
  Rock.AddAction(FTwinkle);
  end;
end;
//------------------------------------------------------------------------------
constructor TSpaceItem.Go;
begin
Create(TPolyArt.Create(CreatePolygon(RandomIntRangeInclusive(3, 6)), RandomColor), TVector.Go, 0, 0,
                      TVector.Go, 1, 0)
end;
//------------------------------------------------------------------------------
procedure TMainForm.Draw(SpaceItem: TSpaceItem);
begin
SpaceItem.Draw(MainForm.FCamara);
end;
//------------------------------------------------------------------------------
procedure TMainForm.DrawSpaceObjects;
begin
if not TrailsCheck.Checked then
  FBackground.Clear;
FSpaceItems.ForEachPassToObject(MainForm, @TMainForm.Draw);
FForground.CopySurface(FBackground);
end;
//------------------------------------------------------------------------------
procedure TMainForm.Drift(SpaceItem: TSpaceItem);
begin
SpaceItem.Drift(FSimInterval);
end;
//------------------------------------------------------------------------------
procedure TMainForm.UpdateSpaceObjects;
begin
FCamara.Surface.UpdateTransformMatrix;
FCamara.UpdateTransformMatrix;
FSpaceItems.Web(@TSpaceItem.GravityPull);
FSpaceItems.ForEachPassToObject(Self, @TMainForm.Drift);
FSpaceItems.React(@TSpaceItem.Collide);
FSpaceItems.RemoveExpiredItems;
FSpaceItems.ForEachCallMethod(@TAgent.FollowActions);
inc(FUpdateCount);
end;
//------------------------------------------------------------------------------
procedure TMainForm.Orbit(Sender: TObject);
var
  UserInputTimer, DrawTimer, StatsTimer, UpdateTimer: TStopWatch;
  Speedometer: TSpeedometer;
begin
UserInputTimer := TStopWatch.Create;
DrawTimer := TStopWatch.Create;
StatsTimer := TStopWatch.Create;
UpdateTimer := TStopWatch.Create;
Speedometer := TSpeedometer.Create;
FOrbiting := true;
repeat
  //Pseudo process thread
  UpdateSpaceObjects;
  Speedometer.Click;
  UpdateTimer.EnsureMSecsElapsedReset(Round(1000/FUpdatesPerSec));

  //Pseudo Output threads
  if DrawTimer.MSecsHaveElapsedReset(1000 div FFramesPerSec) then
    DrawSpaceObjects;
  if StatsTimer.MSecsHaveElapsed(500) then
    begin
    StatusBar.Panels.Items[0].Text := 'Objects '+ToStr(FSpaceItems.ItemCount);
    StatusBar.Panels.Items[1].Text := 'Updates/Sec '+ToStr(Speedometer.ClicksPerSec, -2);
    StatusBar.Panels.Items[2].Text := 'Total updates '+ToStr(FUpdateCount);
    StatusBar.Panels.Items[3].Text := MathMetersToReadableStr(1/FCamara.XScale*ClientWidth)+' across';
    StatsTimer.Reset;
    end;

  //Pseudo input thread
  if UserInputTimer.MSecsHaveElapsedReset(10) then
    Application.ProcessMessages;
  until not FOrbiting;
UserInputTimer.Destroy;
DrawTimer.Destroy;
StatsTimer.Destroy;
Speedometer.Destroy;
UpdateTimer.Destroy;
end;
//------------------------------------------------------------------------------
procedure TMainForm.FormKeyPress(Sender: TObject; var Key: Char);
begin
case Key of
  'w':
    begin
    FTempVec.SetPolar(0.1, FShip.FRot);
    FShip.FVel.Add(FTempVec);
    end;
  'a':
    begin
    if DriftTurnCheck.Checked then
      FShip.FRotVel := FShip.FRotVel+0.02
    else
      FShip.FRot := FShip.FRot+0.15;
    end;
  'd':
    begin
    if DriftTurnCheck.Checked then
      FShip.FRotVel := FShip.FRotVel-0.02
    else
      FShip.FRot := FShip.FRot-0.15;
    end;
  '7': FCamara.Rotate(0.1);//rotate counter-clockwise
  '9': FCamara.Rotate(-0.1);//rotate clockwise
  '8': FCamara.PanPixelsRelSurface(0, 10);//up
  '5': FCamara.PanPixelsRelSurface(0, -10);//down
  '4': FCamara.PanPixelsRelSurface(-10, 0);//left
  '6': FCamara.PanPixelsRelSurface(10, 0);//right
  '+': FCamara.Scale(1.1);//zoom in
  '-': FCamara.Scale(0.9);//zoom out
  '1': FCamara.ScaleXY(1.05, 1);//zoom in
  '0': FCamara.ScaleXY(0.95, 1);//zoom in
  '3': FCamara.ScaleXY(1, 1.05);//zoom in
  '.': FCamara.ScaleXY(1, 0.95);//zoom in
  else
  end;
end;
//------------------------------------------------------------------------------
procedure TMainForm.SetValue(CB: TComboBox; var Variable: TFloat);
begin
if TComboBox(CB).Text = '' then exit;
Variable := StrToFloat(StrDelete(',', StrNthItem(1, TComboBox(CB).Text, ' ')));
end;
//------------------------------------------------------------------------------
procedure TMainForm.UpdateSettings(Sender: TObject);
begin
FFramesPerSec := StrToInt(FramesPerSecEdit.Text);

SetValue(ObjectMassCB, FObjectMass);
SetValue(ObjectMassVariationCB, FObjectMassV);

SetValue(ObjectSpeedCB, FSpeed);
SetValue(ObjectSpeedVariationCB, FSpeedV);

SetValue(ObjectDistanceCB, FDistance);
SetValue(ObjectDistanceVariationCB, FDistanceV);

SetValue(GCB, FG);

SetValue(DistancePowerCB, FDistancePower);

FObjectCount := ToInt(ObjectCountCB.Text);
if UpdatePerSecCB.Text[1] <> 'F' then
  FUpdatesPerSec := ToInt(UpdatePerSecCB.Text)
else
  FUpdatesPerSec := MaxInt;
SetValue(SimIntervalCB, FSimInterval);
end;
//------------------------------------------------------------------------------
procedure TMainForm.DeleteObjectsBtnClick(Sender: TObject);
begin
FSpaceItems.RemoveAllItems;
FUpdateCount := 0;
end;
//------------------------------------------------------------------------------
procedure TMainForm.ResetCamaraBtnClick(Sender: TObject);
begin
FCamara.SetScale(1, 1);
FCamara.SetLoc(0, 0);
end;
//------------------------------------------------------------------------------
procedure TMainForm.FormResize(Sender: TObject);
begin
FCamara.Surface.SetDimentions(ClientWidth, ClientHeight);
FForground.SetDimentions(ClientWidth, ClientHeight);
end;
//------------------------------------------------------------------------------
procedure TSpaceItem.Collide(OtherThing: TSimit);
begin
if Expired then exit;
if OtherThing.Expired then exit;
if Touching(OtherThing) then
  begin
  UpdateMomentum;
  OtherThing.UpdateMomentum;
  FMomentum.Add(OtherThing.FMomentum, FVel);
  FMass := FMass+OtherThing.FMass;
  FVel.SetMagnitude(FVel.Magnitude/FMass);
  OtherThing.FLoc.Subtract(FLoc, MainForm.FTempVec);
  if FMass > OtherThing.FMass then
    begin
    MainForm.FTempVec.ScaleMagnitude(OtherThing.FMass/FMass);
    end
  else
    begin
    MainForm.FTempVec.ScaleMagnitude(1-(FMass/OtherThing.FMass));
    end;
  FLoc.Add(MainForm.FTempVec);
  FScale := Power(FMass, 1/3);
  OtherThing.Expire;
  end;
end;
//------------------------------------------------------------------------------
procedure TSpaceItem.GravityPull(SpaceItem: TSpaceItem);
begin
FLoc.Subtract(SpaceItem.FLoc, MAinForm.FGravity);
MAinForm.FGravity.SetMagnitude(((FMass*MAinForm.FG)/Power(MAinForm.FGravity.Magnitude, MAinForm.FDistancePower))*MAinForm.FSimInterval);
SpaceItem.FVel.Add(MAinForm.FGravity);
end;
//------------------------------------------------------------------------------
procedure TMainForm.StartSimulationBtnClick(Sender: TObject);
begin
Orbit(Sender);
end;
//------------------------------------------------------------------------------
procedure TMainForm.StopSimulationBtnClick(Sender: TObject);
begin
FOrbiting := false;
end;
//------------------------------------------------------------------------------
procedure TMainForm.TimerTimer(Sender: TObject);
begin
Timer.Enabled := false;
AddObjectsBtn.Click;
StartSimulationBtn.Click;
end;
//------------------------------------------------------------------------------
procedure TMainForm.NextMassBtnClick(Sender: TObject);
var
  TempThing: TSpaceItem;
begin
TempThing := TSpaceItem(FSpaceItems.NextItem);
FCamara.SetLoc(TempThing.FLoc);
end;
//------------------------------------------------------------------------------
procedure TMainForm.AddShipBtnClick(Sender: TObject);
begin
FShip := TSpaceItem.Create(
              TPolyArt.Create(GShipShape2, RandomColor),
              TVector.Go,
              0,
              2,
              TVector.Go,
              0,
              4);
FSpaceItems.AddItem(FShip);
end;
//------------------------------------------------------------------------------
class function TTwinkle.CanGuide(Hopeful: TObject): boolean; 
begin
result := Hopeful is TSpaceItem;
end;
//------------------------------------------------------------------------------
procedure TTwinkle.Whatever;
begin
if TSimit(FFollower).FMass >= 100 then
  if Random(Round(TSimit(FFollower).FMass/100)) >= 100 then
    TColoredArt(TSimit(FFollower).FArt).FColor := RandomColor;
end;

end.
